package com.sky.wsp.jar.controller;

import com.sky.wsp.jar.utils.CmdUtil;
import com.sky.wsp.jar.utils.IOUtil;
import com.sky.wsp.jar.utils.ZipUtil;
import com.sky.wsp.jar.vo.RepackageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@RestController
public class TransformerTools {
    private static Pattern SPRING_CLOUD_STARTER_GATEWAY_PATTERN = Pattern.compile("spring-cloud-starter-gateway-([0-9]).([0-9])+.([0-9])+.*.jar");
    private static Pattern SPRING_BOOT_STARTER_PATTERN = Pattern.compile("spring-boot-starter-([0-9]).([0-9])+.([0-9])+.*.jar");
    private static Pattern SPRING_BOOT_PATTERN = Pattern.compile("spring-boot-([0-9]).([0-9])+.([0-9])+.*.jar");
    private static String[] excludeJars = new String[]{"HulkCP", "tongweb-spring-boot-data-jdbc-starter", "tongweb-spring-boot-maven-plugin", "tongweb-plugin", ".pom"};
    private static String[] nettyJars = new String[]{"reactor-netty-core", "reactor-netty-http"};
    private static final String TOMCAT = "tomcat";
    private static final String UNDERTOW = "undertow";
    private static final String TONGWEB = "tongweb";
    private static final String LICENSE_FILE_NAME = "license.dat";

    private static final Logger log = LoggerFactory.getLogger(TransformerTools.class);

    private RepackageInfo initParam(RepackageInfo param) {
        RepackageInfo repackageInfo = new RepackageInfo();

        // 要改造的SpringBoot fat jar 包路径
//        repackageInfo.setSrcJarPath("D:\\002XCSP\\JYJ\\00tongweb\\springboot");
        repackageInfo.setSrcJarPath(param.getSrcJarPath());

        // TongWeb证书文件、yml和property配置文件模板的存放路径
//        repackageInfo.setLicenseAndConfigPath("D:\\002XCSP\\JYJ\\00tongweb\\embed");
        repackageInfo.setLicenseAndConfigPath(param.getLicenseAndConfigPath());

        // 嵌入式TongWeb的lib包路径
//        repackageInfo.setEmbedPath("D:\\002XCSP\\JYJ\\00tongweb\\embed\\lib");
        repackageInfo.setEmbedPath(param.getEmbedPath());

        // TongWeb-Reactor的lib包路径
//        repackageInfo.setEmbedReactorPath("D:\\002XCSP\\JYJ\\00tongweb\\embed\\reactor-lib");
        repackageInfo.setEmbedReactorPath(param.getEmbedReactorPath());
        return repackageInfo;
    }

    @GetMapping("/repackage")
    public RepackageInfo repackage(RepackageInfo param) {
        if (null == param
                || null == param.getSrcJarPath()
                || null == param.getEmbedPath()
                || null == param.getEmbedReactorPath()
                || null == param.getLicenseAndConfigPath()
        ) {
            log.error("请在页面上把配置信息填写完整！！！！");
            return param;
        }
        RepackageInfo repackageInfo = initParam(param);
        String srcJarPath = repackageInfo.getSrcJarPath();
        File srcJarFile = new File(srcJarPath);
        if (!srcJarFile.exists()) {
            log.error("原Jar包路径不存在！！！！");
            return repackageInfo;
        }
        if (srcJarFile.isDirectory()) {
            // 如果给定的是目录
            File[] files = srcJarFile.listFiles();
            for (int i = 0; i < files.length; i++) {
                File file = files[i];
                if (file.isDirectory()) {
                    continue;
                }
                // 每次都要重新初始化一次
                repackageInfo = initParam(param);
                log.info("开始转换：{}", file.getAbsolutePath());
                doRepackage(file, repackageInfo);
            }
        } else {
            // 如果给定的是SpringBoot jar 全路径
            File file = new File(srcJarPath);
            log.info("开始转换：{}", file.getAbsolutePath());
            doRepackage(file, repackageInfo);
        }
        return repackageInfo;
    }

    private void doRepackage(File srcJarFile, RepackageInfo repackageInfo) {
        // app.jar
        String appJarName = srcJarFile.getName();
        int index = appJarName.lastIndexOf(".");
        if (index < 0) {
            log.warn("只支持处理jar文件, path: " + srcJarFile.getAbsolutePath());
            return;
        }
        String appExt = appJarName.substring(index);
        if (!".jar".equals(appExt)) {
            log.warn("只支持处理jar文件, path: " + srcJarFile.getAbsolutePath());
            return;
        }
        // app
        String appSimpleName = appJarName.substring(0, index);
        // D:\002XCSP\JYJ\00tongweb\springboot\app
        String targetPath = srcJarFile.getParent() + File.separator + TONGWEB + File.separator + appSimpleName;
        try {
            ZipUtil.unZipToDir(srcJarFile, new File(targetPath));
            log.info("unzip jar file , path: {}", targetPath);
        } catch (Exception e) {
            log.error("unzip srcJar failed,please confirm your SRC_JAR_PATH are correct", e);
            return;
        }

        // 根据要改造jar包的依赖情况，初始化配置参数
        initPackageConfig(repackageInfo, targetPath);

        // D:\002XCSP\JYJ\00tongweb\springboot\app-tw\BOOT-INF\lib
        String copyTongWebDepsPath = targetPath + File.separator + "BOOT-INF" + File.separator + "lib";
        copyTongWebDependencies(repackageInfo, copyTongWebDepsPath);

        // D:\002XCSP\JYJ\00tongweb\springboot\app-tw\BOOT-INF\classes
        String targetLicensePath = targetPath + File.separator + "BOOT-INF" + File.separator + "classes";
        copyLicense(repackageInfo.getLicenseAndConfigPath() + File.separator + LICENSE_FILE_NAME, targetLicensePath);

        // D:\002XCSP\JYJ\00tongweb\springboot\app-tw\BOOT-INF\classes
        addTongWebConfigContent(repackageInfo.getLicenseAndConfigPath(), targetPath);

        log.info("执行jar命令打包，打包路径：" + targetPath);
        // D:\002XCSP\JYJ\00tongweb\springboot\app-tw.jar
        String targetJarName = targetPath + ".jar";
        CmdUtil.callJarCmd(targetJarName, targetPath);
        log.info("最终打包生成文件：" + targetJarName);

        IOUtil.forceDelete(new File(targetPath));
    }

    public static void initPackageConfig(RepackageInfo repackageInfo, String targetPath) {
        List<String> fileList = new ArrayList();
        List<String> files = getFiles(targetPath + File.separator + "BOOT-INF" + File.separator + "lib", fileList);
        for (String dependencyJarName : files) {
            File srcFile = new File(dependencyJarName);
            if (srcFile.isDirectory()) {
                // 空目录则可以删除
            } else {
                dependencyJarName = srcFile.getName();
                if (dependencyJarName.contains(TOMCAT) || dependencyJarName.contains(UNDERTOW) || hasNettyJar(dependencyJarName)) {
                    repackageInfo.getTomcatList().add(dependencyJarName);
                    // TODO 需要删除的tomcat/undertow jar 包
                    IOUtil.forceDelete(srcFile);
                    continue;
                }
                Matcher gateway = SPRING_CLOUD_STARTER_GATEWAY_PATTERN.matcher(dependencyJarName);
                if (gateway.matches()) {
                    repackageInfo.setNeedNetty(true);
                }
                Matcher m = SPRING_BOOT_STARTER_PATTERN.matcher(dependencyJarName);
                if (m.matches()) {
                    repackageInfo.getSpringBootStarterList().add(dependencyJarName);
                    continue;
                }
                m = SPRING_BOOT_PATTERN.matcher(dependencyJarName);
                if (m.matches()) {
                    repackageInfo.getSpringBootList().add(dependencyJarName);
                }
            }
        }
        // 根据SpringBoot依赖包，判断SpringBoot版本
        String versionSuffix;
        if (!repackageInfo.getSpringBootStarterList().isEmpty()) {
            versionSuffix = repackageInfo.getSpringBootStarterList().get(0).substring("spring-boot-starter-".length());
        } else if (!repackageInfo.getSpringBootList().isEmpty()) {
            versionSuffix = repackageInfo.getSpringBootList().get(0).substring("spring-boot-".length());
        } else {
            log.error("spring boot version not found. please make sure the dependencies spring-boot-[version].jar or spring-boot-starter-[version].jar  are in your project ");
            return;
        }
        repackageInfo.setStarter1x(versionSuffix.startsWith("1."));

        for (String tomcatDependencyName : repackageInfo.getTomcatList()) {
            if (tomcatDependencyName.contains("jasper")) {
                repackageInfo.setNeedJspJar(true);
            }
            if (tomcatDependencyName.contains("websocket")) {
                repackageInfo.setNeedWebsocketJar(true);
            }
        }
    }

    public static boolean hasNettyJar(String jarName) {
        for (int i = 0; i < nettyJars.length; ++i) {
            if (jarName.startsWith(nettyJars[i])) {
                return true;
            }
        }
        return false;
    }

    public static void copyLicense(String licensePath, String targetPath) {
        String licenseTargetPath = targetPath + File.separator + LICENSE_FILE_NAME;

        try {
            File srcLicenseFile = new File(licensePath);
            File targetLicenseFile = new File(licenseTargetPath);
            IOUtil.copyFile(srcLicenseFile, targetLicenseFile);
        } catch (IOException e) {
            log.error("Copy license file failed. If you need to automatically copy license.dat file, place license.dat file in the same directory as your project", e);
        }

    }

    public static void addTongWebConfigContent(String templateFilePath, String targetPath) {
        if (StringUtils.isEmpty(templateFilePath)) {
            log.error("templateFilePath :[" + templateFilePath + "] not exists!");
            return;
        }
        List<File> configFileList = getConfigFilePath(targetPath);
        if (configFileList.isEmpty()) {
            log.error("can't find application.properties or application.yml or bootstrap.yml in path :[" + targetPath + "]!");
            return;
        }

        boolean hasConfig = false;
        try {
            String propTemplateFilePath = templateFilePath + File.separator + "config_template.properties";
            String propTemplateContent = IOUtil.readFile(new File(propTemplateFilePath));
            String ymlTemplateFilePath = templateFilePath + File.separator + "config_template.yml";
            String ymlTemplateContent = IOUtil.readFile(new File(ymlTemplateFilePath));
            for (File configFile : configFileList) {
                // 如果存在properties文件则优先使用，因为properties容易替换
                if (!configFile.getName().endsWith(".properties")) {
                    continue;
                }
                String configFileContent = IOUtil.readFile(configFile);
                configFileContent = configFileContent.replaceAll(TOMCAT, TONGWEB).replaceAll(UNDERTOW, TONGWEB);
                IOUtil.reWriteContent2File(configFileContent, configFile);
                if (hasConfig) {
                    continue;
                }
                IOUtil.appendTailContent2File(propTemplateContent, configFile);
                log.info("tongweb 配置位置：{}", configFile.getAbsolutePath());
                hasConfig = true;
            }

            for (File configFile : configFileList) {
                if (!configFile.getName().endsWith(".yml") && !configFile.getName().endsWith(".yaml")) {
                    continue;
                }
                String configFileContent = IOUtil.readFile(configFile);
                configFileContent = configFileContent.replaceAll(TOMCAT, TONGWEB).replaceAll(UNDERTOW, TONGWEB);
                IOUtil.reWriteContent2File(configFileContent, configFile);
                if (hasConfig) {
                    continue;
                }
                // TODO yml格式配置文件，添加tongweb配置
                boolean b = addYmlConfig(ymlTemplateContent, configFile);
                if (b) {
                    log.info("tongweb 配置位置：{}", configFile.getAbsolutePath());
                    hasConfig = true;
                }
//                templateFilePath = templateFilePath + File.separator + "config_template.yml";
//                Map<String, Object> targetYamlFileContent = IOUtil.customizeAppendContent2Yml(new File(templateFilePath), configFile);
//                IOUtil.createConfigYaml(targetYamlFileContent, configFile.getCanonicalPath());
            }
        } catch (IOException e) {
            log.error("append tongweb config content failed.", e);
        }
        if (!hasConfig) {
            log.error("添加tongweb授权证书配置失败，请手动配置！");
        }
    }

    private static boolean addYmlConfig(String ymlTemplateContent, File configFile) {
        StringBuilder result = new StringBuilder();
        final List<Character> tab = new ArrayList<>();
        InputStreamReader isr = null;
        try {
            isr = new InputStreamReader(Files.newInputStream(configFile.toPath()), StandardCharsets.UTF_8);
            if (null == isr) {
                log.error("read yml config error!");
                return false;
            }
            BufferedReader bufferedReader = new BufferedReader(isr);
            bufferedReader.lines().forEach((item) -> {
                result.append(item).append("\r\n");
                if (tab.isEmpty()) {
                    // 数一下yml的缩进是几个空格，有2个的，有3个的，有4个的...
                    for (int i = 0; i < item.length(); i++) {
                        if (item.charAt(i) == ' ') {
                            tab.add(' ');
                        } else {
                            break;
                        }
                    }
                }
            });
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    log.error("read yml config error!", e);
                    return false;
                }
            }
        } catch (Exception e) {
            log.error("read yml config error!", e);
            return false;
        } finally {
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                    log.error("read yml config error!", e);
                }
            }
        }
        // 提供的yml模板，和应用的yml配置，可能使用的tab缩进不一致，所以要进行统一
        String formatTemplate = formatYmlTemplateContent(ymlTemplateContent, tab);
        if (result.indexOf("tongweb:") > 0) {
            String server = "server:\r\n";
            for (int i = 0; i < tab.size(); i++) {
                server = server + ' ';
            }
            String tongwebcontent = formatTemplate.replace(server, "");
            int idx = result.indexOf("tongweb:");
            result.replace(idx, idx + 8, tongwebcontent);
        } else if (result.indexOf("server:") >= 0) {
            int idx = result.indexOf("server:");
            result.replace(idx, idx + 7, formatTemplate);
        } else {
            log.warn("{}中没有server配置级别！", configFile.getAbsolutePath());
            return false;
        }
        try {
            IOUtil.reWriteContent2File(result.toString(), configFile);
        } catch (IOException e) {
            log.error("写入yml配置文件失败!", e);
            return false;
        }
        return true;
    }

    private static String formatYmlTemplateContent(String ymlTemplateContent, List<Character> tab) {
        String[] split = ymlTemplateContent.split("\r\n");
        for (String line : split) {
            if (line.trim().equals("tongweb:")) {
                String tempTab = "";
                for (int i = 0; i < line.length(); i++) {
                    if (line.charAt(i) == ' ') {
                        tempTab = tempTab + ' ';
                    } else {
                        break;
                    }
                }
                if (tab.size() != tempTab.length()) {
                    // 提供的yml模板，和应用的yml配置，可能使用的tab缩进不一致
                    String confTab = "";
                    for (int i = 0; i < tab.size(); i++) {
                        confTab = confTab + ' ';
                    }
                    ymlTemplateContent.replaceAll(tempTab, confTab);
                }
                break;
            }
        }
        return ymlTemplateContent;
    }

    public static void copyTongWebDependencies(RepackageInfo repackageInfo, String targetPath) {
        String embedPath = repackageInfo.isNeedNetty() ? repackageInfo.getEmbedReactorPath() : repackageInfo.getEmbedPath();
//    String embedPath = System.getProperty("TONGWEB_EMBED_LIB_PATH");
        if (null == embedPath || embedPath.equals("")) {
            log.error("Transform failed. TongWeb embed lib path not found.");
            return;
        }
        if (embedPath.endsWith("\\") || embedPath.endsWith("/")) {
            embedPath = embedPath.substring(0, embedPath.length() - 1);
        }

        List<String> filelist = new ArrayList();
        List<String> embedLibs = getFiles(embedPath, filelist);
        if (!IOUtil.checkLibPath(embedLibs)) {
            log.error("Invalid TONGWEB_EMBED_LIB_PATH, Please make sure your parameters TONGWEB_EMBED_LIB_PATH are tongweb-embed path");
            return;
        }

        for (String embedLibPath : embedLibs) {
            File embedLibFile = new File(embedLibPath);
            boolean transformFlag = checkTransformFlag(repackageInfo, embedLibFile.getName());
            if (!transformFlag) {
                continue;
            }
            try {
                String targetFilePath = embedLibFile.getCanonicalPath().replace(embedPath, targetPath);
                File targetFile = new File(targetFilePath);
                IOUtil.copyFile(embedLibFile, targetFile);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static boolean checkTransformFlag(RepackageInfo repackageInfo, String fileName) {
        boolean transformFlag = true;

        for (int i = 0; i < excludeJars.length; ++i) {
            String excludeNameJar = excludeJars[i];
            if (fileName.contains(excludeNameJar)) {
                return false;
            }
        }

        if (repackageInfo.isStarter1x()) {
            if (fileName.startsWith("tongweb-spring-boot-starter-2.x")) {
                transformFlag = false;
            }

            if (fileName.startsWith("tongweb-spring-boot-monitor-starter-2.x")) {
                transformFlag = false;
            }
        } else {
            if (fileName.startsWith("tongweb-spring-boot-starter-1.x")) {
                transformFlag = false;
            }

            if (fileName.startsWith("tongweb-spring-boot-monitor-starter-1.x")) {
                transformFlag = false;
            }
        }

        if (!repackageInfo.isNeedJspJar() && fileName.startsWith("tongweb-jsp")) {
            transformFlag = false;
        }

        if (!repackageInfo.isNeedWebsocketJar()) {
            if (fileName.startsWith("tongweb-spring-boot-websocket")) {
                transformFlag = false;
            }
        } else if (repackageInfo.isStarter1x()) {
            if (fileName.startsWith("tongweb-spring-boot-websocket-2.x")) {
                transformFlag = false;
            }
        } else if (fileName.startsWith("tongweb-spring-boot-websocket-1.x")) {
            transformFlag = false;
        }

        return transformFlag;
    }

    public static List<String> getFiles(String filePath, List<String> filelist) {
        File root = new File(filePath);
        if (!root.exists()) {
            log.error(filePath + " is not exists!");
        } else {
            File[] files = root.listFiles();
            if (files.length == 0) {
                filelist.add(filePath);
            }
            for (int i = 0; i < files.length; ++i) {
                File file = files[i];
                if (file.isDirectory()) {
                    getFiles(file.getAbsolutePath(), filelist);
                } else {
                    filelist.add(file.getAbsolutePath());
                }
            }
        }

        return filelist;
    }

    private static List<File> getConfigFilePath(String classBasePath) {
        File classBaseFile = new File(classBasePath);
        String currentJarBasePath = classBaseFile.getParentFile().getPath();
        List<String> pathList = new ArrayList();
        String inClassDirectPath = classBasePath + File.separator + "BOOT-INF" + File.separator + "classes";
        pathList.add(inClassDirectPath);

        String inClassDirectConfigPath = classBasePath + File.separator + "BOOT-INF" + File.separator + "classes" + File.separator + "config";
        pathList.add(inClassDirectConfigPath);

        String inCurrentJarConfigPath = currentJarBasePath + File.separator + "config";
        pathList.add(inCurrentJarConfigPath);
        pathList.add(currentJarBasePath);

        List<File> configFileList = new ArrayList();
        for (String configPath : pathList) {
            includeConfigFile(configPath, configFileList);
        }
        return configFileList;
    }

    private static void includeConfigFile(String path, List<File> configFileList) {
        File root = new File(path);
        log.info("Look for config file automatically in the directory : [" + path + "]");
        if (!root.exists()) {
            log.warn("config file not found in path : [" + path + "]");
            return;
        }

        File[] files = root.listFiles();
        for (File file : files) {
            if (!file.isDirectory() && (
                    file.getName().equals("application.properties")
                            || file.getName().equals("bootstrap.properties")
                            || file.getName().equals("bootstrap.yml")
                            || file.getName().equals("bootstrap.yaml")
                            || file.getName().equals("application.yml")
                            || file.getName().equals("application.yaml")
            )) {
                configFileList.add(file);
                log.info("found config file : [" + file.getAbsolutePath() + "]");
            }
        }
    }
}
